/*
 * Copyright (C) 2014 Square, Inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
import { MediaType } from './MediaType'
import { RequestBody } from './RequestBody'
import { Charset } from './utils/Charset'
import { buffer, util } from '@kit.ArkTS'
import { Sink } from './okio/Sink'
import { HttpUrl } from './HttpUrl'


export class FormBody extends RequestBody {
  private static readonly CONTENT_TYPE: MediaType = MediaType.toMediaType("application/x-www-form-urlencoded")
  private readonly encodedNames: Array<string>
  private readonly encodedValues: Array<string>
  
  /** Internal constructor - use Builder to create instances */
  private constructor(encodedNames: Array<string>, encodedValues: Array<string>) {
    super()
    // Defensive copy to ensure immutability
    this.encodedNames = [...encodedNames]
    this.encodedValues = [...encodedValues]
  }

  /** The number of key-value pairs in this form-encoded body. */
  get size(): number{
    return this.encodedNames.length
  }

  encodedName(index: number) {
    return this.encodedNames[index]
  }

  name(index: number) {
    return HttpUrl.percentDecode(this.encodedName(index), undefined, undefined, true)
  }

  encodedValue(index: number){
    return this.encodedValues[index]
  }

  value(index: number) {
    return HttpUrl.percentDecode(this.encodedValue(index), undefined, undefined, true)
  }

  override contentType(): MediaType | null {
    return FormBody.CONTENT_TYPE
  }

  override contentLength() {
    return this.writeOrCountBytes(null, true)
  }

  // @Throws(IOException::class)
  override async writeTo(sink: Sink) {
    await this.writeOrCountBytes(sink, false)
  }

  /**
   * Either writes this request to [sink] or measures its content length. We have one method
   * do double-duty to make sure the counting and content are consistent, particularly when it comes
   * to awkward operations like measuring the encoded length of header strings, or the
   * length-in-digits of an encoded integer.
   */
  private async writeOrCountBytes(sink: Sink | null, countBytes: boolean): Promise<number> {
    let byteCount = 0
    let tempBuffer: buffer.Buffer | null = null
    let targetSink: Sink

    if (countBytes) {
      tempBuffer = buffer.from("")
      // Create a temporary sink-like object for counting
      targetSink = {
        write: (data: string, length: number) => {
          tempBuffer = buffer.concat([tempBuffer!, buffer.from(data)])
        }
      } as Sink
    } else {
      targetSink = sink!
    }

    for (let i = 0; i < this.encodedNames.length; i++) {
      if (i > 0) await targetSink.write('&', 1)
      await targetSink.write(this.encodedNames[i], this.encodedNames[i].length)
      await targetSink.write('=', 1)
      await targetSink.write(this.encodedValues[i], this.encodedValues[i].length)
    }

    if (countBytes && tempBuffer !== null && tempBuffer !== undefined) {
      byteCount = tempBuffer.buffer.byteLength
    }

    return byteCount
  }

  static Builder = class {
    #names = new Array<string>()
    #values = new Array<string>()
    #charset: Charset | null
    
    constructor(charset: Charset | null = null) {
      this.#charset = charset
    }

    add(name: string, value: string) {
      this.#names.push(
        HttpUrl.canonicalize(
          name,
          0,
          name.length,
          HttpUrl['FORM_ENCODE_SET'],
          false,
          false,
          true,
          false,
          this.#charset?.name()
        )
      );
      this.#values.push(
        HttpUrl.canonicalize(
          value,
          0,
          value.length,
          HttpUrl['FORM_ENCODE_SET'],
          false,
          false,
          true,
          false,
          this.#charset?.name()
        )
      );
      return this;
    }

    addEncoded(name: string, value: string) {
      this.#names.push(
        HttpUrl.canonicalize(
          name,
          0,
          name.length,
          HttpUrl['FORM_ENCODE_SET'],
          true,
          false,
          true,
          false,
          this.#charset?.name()
        )
      );
      this.#values.push(
        HttpUrl.canonicalize(
          value,
          0,
          value.length,
          HttpUrl['FORM_ENCODE_SET'],
          true,
          false,
          true,
          false,
          this.#charset?.name()
        )
      );
      return this;
    }

    build(): FormBody{
      return new FormBody(this.#names, this.#values);
    }
  }

}
